package com.zhongkun.datahome.service.processor;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSON;
import com.zhongkun.datahome.common.exception.ServiceException;
import com.zhongkun.datahome.service.processor.dto.SkuDto;
import com.zhongkun.datahome.service.res.InvalidExcelDataVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.*;

/**
 * @Author: lifengsheng
 * @Date: 2023/8/9 14:43
 * @Desc 有个很重要的点 DataListener 不能被spring管理，要每次读取excel都要new
 */
@Slf4j
public class SkuDataProcessor extends AnalysisEventListener<SkuDto> {
    /**
     * 动态上传 限制一个excel文件上限为1000条数据，保证全部数据一次性保存到数据库，方便事物管理
     */
    private static final int MAX_COUNT = 3000;

    /**
     * 缓存的数据
     */
    private List<SkuDto> cachedDataList;

    private List<InvalidExcelDataVo> invalidExcelDataVoList;

    private Map<Integer, String> headMap;

    private ValidatorFactory validatorFactory;

    private Validator validator;

    public List<SkuDto> getCachedDataList() {
        return this.cachedDataList;
    }

    public SkuDataProcessor() {
        this.cachedDataList = ListUtils.newArrayListWithExpectedSize(MAX_COUNT);
        this.invalidExcelDataVoList = new ArrayList<>();
        this.headMap = new HashMap<>();
        this.validatorFactory = Validation.buildDefaultValidatorFactory();
        this.validator = this.validatorFactory.getValidator();
    }

    /**
     * 每一条数据解析成功都会调用该方法
     *
     * @param skuDto 一条数据
     * @param analysisContext context
     */
    @Override
    public void invoke(SkuDto skuDto, AnalysisContext analysisContext) {
        // log.info("解析到一条数据:{}", JSON.toJSONString(skuDto));

        Integer rowIndex = analysisContext.readRowHolder().getRowIndex();
        skuDto.setRowIndex(rowIndex);

        // 校验数据合法性
        checkData(skuDto, rowIndex);

        cachedDataList.add(skuDto);

        // 这里限制一个excel文件上限为1000条数据，保证全部数据一次性保存到数据库，方便事物管理
        if (cachedDataList.size() > MAX_COUNT) {
            throw new ServiceException("每个excel文件最多支持上传" + MAX_COUNT +"条动态");
        }
    }

    /**
     * 每一条数据解析异常都会调用该方法
     *
     * @param exception
     * @param context
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("read excel data occur error: {}", exception.getMessage());

        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException e = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列解析异常，数据为: {}", e.getRowIndex(), e.getColumnIndex(), e.getCellData().getStringValue());

            // 保存解析异常的信息，继续解析下一行数据
            invalidExcelDataVoList.add(InvalidExcelDataVo.of(e.getRowIndex(),
                    this.headMap.get(e.getColumnIndex()), "解析异常", e.getCellData().getStringValue()));
            return;
        }

        throw new ServiceException("表格读取异常");
    }

    /**
     * 会在每个sheet读取完毕后调用一次
     *
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        this.headMap.clear();
        this.validatorFactory.close();
        log.info("当前sheet所有数据解析完成！");

        // 有异常数据，要抛给前端展示
        if (CollectionUtils.isNotEmpty(invalidExcelDataVoList)) {
            this.cachedDataList.clear();
            throw new ServiceException(getErrorMessage(invalidExcelDataVoList));
        }
    }

    private String getErrorMessage(List<InvalidExcelDataVo> invalidExcelDataVoList) {
        StringBuilder errorMessage = new StringBuilder();
        invalidExcelDataVoList.forEach(item -> errorMessage.append("第").append(item.getRow()).append("行")
                .append(item.getMessage()).append(";").append(System.lineSeparator()));
        return errorMessage.toString();
    }

    /**
     * 获取表头信息
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        if (this.headMap.isEmpty()) {
            log.info("excel headList is: {}", JSON.toJSONString(headMap));
            this.headMap.putAll(headMap);
        }
    }

    private void checkData(SkuDto data, Integer rowIndex) {
        Set<ConstraintViolation<SkuDto>> invalidMessageSet = this.validator.validate(data);

        if (CollectionUtils.isNotEmpty(invalidMessageSet)) {
            invalidMessageSet.forEach(item ->
                    invalidExcelDataVoList.add(InvalidExcelDataVo.of(rowIndex, item.getMessage(), item.getInvalidValue())));
        }
    }
}
